package jmt.engine.NetStrategies.RoutingStrategies;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;

import jmt.engine.NetStrategies.RoutingStrategy;
import jmt.engine.QueueNet.JobClass;
import jmt.engine.QueueNet.NetNode;
import jmt.engine.QueueNet.NodeList;

public class EnergyAwareRoutingStrategy extends RoutingStrategy {
	
	private static final int rowOrder = 100;
	
	private HashMap<Integer, Double> optimalBetas;
	private HashMap<Integer, Integer> numMaxJobs;
	
	public EnergyAwareRoutingStrategy(HashMap<Integer, Double> optimalBetas, HashMap<Integer, Integer> numMaxJobs) {
		this.optimalBetas = optimalBetas;
		this.numMaxJobs = numMaxJobs;
	}
	
	@Override
	public NetNode getOutNode(NodeList CPUs, JobClass jobClass) {
		if(CPUs.size() != 0) {
			try {
				NetNode out = getTargetNode(CPUs, jobClass);
//				System.out.println("EARS(27) -> Invio al nodo "+out.getName()+" un job di classe "+(jobClass.getId()+1));
//				System.out.println("----------------------------------");
				return out;
			} catch (jmt.common.exception.NetException e) {
				e.printStackTrace();
			}
		}
		return null;
	}
	
	private NetNode getTargetNode(NodeList CPUs, JobClass jobClass) throws jmt.common.exception.NetException {
		HashMap<Integer, Double> actualBetas = new HashMap<Integer, Double>();
		HashMap<Integer, Boolean> freeSpaceInSS = new HashMap<Integer, Boolean>();
		HashMap<Integer, Double> residualBetas = new HashMap<Integer, Double>();
		ArrayList<Integer> chosenSubsystems = new ArrayList<Integer>(); //It is used to take into consideration all the minimum subsystems
		Random randomGenerator = new Random(); //Random generator to choose a random minimum subsystem for the jobs forwarding

		actualBetas = getNextHopActualBetas(CPUs, jobClass);
		freeSpaceInSS = getFreeSpaceInSS(CPUs, jobClass);
		for(int i=0; i<CPUs.size(); i++) {
//				System.out.println("EARS(45) -> |"+optimalBetas.get(i*rowOrder+jobClass.getId())+" - "+actualBetas.get(i)+"| = "+Math.abs(optimalBetas.get(i*rowOrder+jobClass.getId()) - actualBetas.get(i)));
				residualBetas.put(i, Math.abs(optimalBetas.get(i*rowOrder+jobClass.getId()) - actualBetas.get(i)));
		}
		
		double min = 2.0;
		//Integer subsystem = null;
		for(int i=0; i<CPUs.size(); i++) {
			double residualBeta = (double) residualBetas.get(i);
			//If the subsystem is the minimum then update the variable min, remove all elements from the list and add the new one
			if((boolean) freeSpaceInSS.get(i)) {
				if(residualBeta < min) {
					min = residualBeta;
					if(!chosenSubsystems.isEmpty()) {
						chosenSubsystems.clear();
					}
					chosenSubsystems.add(i);
				// If the subsystem is one of the minimum then add it to the list
				} else if(residualBeta == min) {
					chosenSubsystems.add(i);
				}
			}			
			//Delete when debug is ended --- START
//			if(freeSpaceInSS.get(i)) {
//				System.out.println("EARS(59) -> Il subsystem "+(i+1)+" ha ancora spazio libero");
//			} else {
//				System.out.println("EARS(61) -> Il subsystem "+(i+1)+" NON ha più spazio libero");
//			}
			//END
		}
		if(!chosenSubsystems.isEmpty()) {
			//The subsystem is chosen randomly among those in the list (i.e. the minimum)
			int index = randomGenerator.nextInt(chosenSubsystems.size());
			int subsystem = chosenSubsystems.get(index);
			return CPUs.get(subsystem);
		} else {
			return null;
		}
	}
	
	private HashMap<Integer, Double> getNextHopActualBetas(NodeList CPUs, JobClass jobClass) throws jmt.common.exception.NetException {
		HashMap<Integer, Double> actualBetas = new HashMap<Integer, Double>();
		double totJobsInSS = 0.0;
		double totJobsInSSPerClass = 0.0;
		for(int i=0; i<CPUs.size(); i++) {
			totJobsInSS = CPUs.get(i).getIntNodeProperty(NetNode.PROPERTY_ID_RESIDENT_JOBS);
			totJobsInSSPerClass = CPUs.get(i).getIntNodeProperty(NetNode.PROPERTY_ID_RESIDENT_JOBS, jobClass);
			NodeList IOs = CPUs.get(i).getOutputNodes();
			for(int j=0; j<IOs.size(); j++) {
				totJobsInSS += IOs.get(j).getIntNodeProperty(NetNode.PROPERTY_ID_RESIDENT_JOBS);
				totJobsInSSPerClass += IOs.get(j).getIntNodeProperty(NetNode.PROPERTY_ID_RESIDENT_JOBS, jobClass);
			}
			totJobsInSS += 1; //This is the job that router will send in the next hop
			totJobsInSSPerClass += 1; //This is the job that router will send in the next hop
			
			actualBetas.put(i, totJobsInSSPerClass/totJobsInSS);
		}
		return actualBetas;
	}
	
	private HashMap<Integer, Boolean> getFreeSpaceInSS(NodeList CPUs, JobClass jobClass) throws jmt.common.exception.NetException {
		HashMap<Integer, Boolean> freeSpaceInSS = new HashMap<Integer, Boolean>();
		int totJobsInSS = 0;
		for(int i=0; i<CPUs.size(); i++) {
			totJobsInSS = CPUs.get(i).getIntNodeProperty(NetNode.PROPERTY_ID_RESIDENT_JOBS);
			NodeList IOs = CPUs.get(i).getOutputNodes();
			for(int j=0; j<IOs.size(); j++) {
				totJobsInSS += IOs.get(j).getIntNodeProperty(NetNode.PROPERTY_ID_RESIDENT_JOBS);
			}
			if(totJobsInSS < numMaxJobs.get(i)) {
				freeSpaceInSS.put(i, true);
			} else {
				freeSpaceInSS.put(i, false);
			}
		}
		return freeSpaceInSS;
	}
	
	
	

}
